#include "device_simulator/rtsp_server.h"

inline timeval DURING_TIMEVAL(timeval base_tv) {
  struct timeval now_tv;
  evutil_gettimeofday(&now_tv, NULL);

  struct timeval during_tv;
  evutil_timersub(&now_tv, &base_tv, &during_tv);
  return during_tv;
}

static void VideoTimerCallback(evutil_socket_t fd, int16_t flags, void *ctx) {
  RtspServer::ClientInfo *client =
    reinterpret_cast<RtspServer::ClientInfo*>(ctx);
  if (!evtimer_pending(client->video_evtimer, NULL)) {
    client->rtsp_server->OnVideoTimer(client->async_socket);
    evtimer_del(client->video_evtimer);
    evtimer_add(client->video_evtimer, &client->video_tvtimer);
  }
}

RtspServer::RtspServer(EventLoop::Ptr event_loop)
  : event_loop_(event_loop)
  , listener_(NULL) {
}


RtspServer::~RtspServer() {
}

bool RtspServer::Start(const std::string &ip, int port) {
  listener_ = Listener::Ptr(new Listener(event_loop_));
  listener_->SignalAccept.connect(
    this, &RtspServer::OnListenerAccept);
  listener_->SignalError.connect(
    this, &RtspServer::OnListenerError);
  return listener_->Start(ip, port);
}

void RtspServer::OnListenerAccept(Listener::Ptr listener,
                                  evutil_socket_t fd,
                                  struct sockaddr *saddr,
                                  int slen) {
  AsyncSocket::Ptr async_socket(new AsyncSocket(event_loop_, fd));
  async_socket->SignalPacket.connect(
    this, &RtspServer::OnAsyncSocketPacket);
  async_socket->SignalError.connect(
    this, &RtspServer::OnAsyncSocketError);
  async_socket->SignalClosed.connect(
    this, &RtspServer::OnAsyncSocketClosed);

  if (async_socket->Initialize()) {
    ClientInfo *client = new ClientInfo();
    client->rtsp_server = shared_from_this();
    client->async_socket = async_socket;
    client->video_tvtimer.tv_sec = 0;
    client->video_tvtimer.tv_usec = 40 * 1000;
    client->video_evtimer = NULL;
    client_map_.insert(std::make_pair(async_socket, client));
  }
}

void RtspServer::OnListenerError(Listener::Ptr listener) {
}

void RtspServer::OnAsyncSocketPacket(AsyncSocket::Ptr async_socket,
                                     uint16_t cmd,
                                     const char *data,
                                     uint32_t data_size) {
  ClientInfo *client = client_map_[async_socket];

  switch (cmd) {
  case CMD_HELLO: {
    memcpy(&client->base_tv, data, sizeof(client->base_tv));
    client->base_tv = NTOH_TIMEVAL(client->base_tv);

    struct timeval during_tv = DURING_TIMEVAL(client->base_tv);
    during_tv = HTON_TIMEVAL(during_tv);

    async_socket->AsyncSend(CMD_HELLO_ACK,
                            (const char*)&during_tv,
                            sizeof(during_tv));
    break;
  }
  case CMD_START_PLAY: {
    client->video_evtimer = evtimer_new(event_loop_->GetEventBase(),
                                        VideoTimerCallback,
                                        client);
    event_add(client->video_evtimer, &client->video_tvtimer);
    break;
  }
  }
}

void RtspServer::OnAsyncSocketError(AsyncSocket::Ptr async_socket,
                                    int error_code) {
  ClientInfo *client = client_map_[async_socket];
  if (NULL != client->video_evtimer) {
    if (!evtimer_pending(client->video_evtimer, NULL)) {
      evtimer_del(client->video_evtimer);
    }
    event_free(client->video_evtimer);
  }
  delete client;
  client_map_.erase(async_socket);
}

void RtspServer::OnAsyncSocketClosed(AsyncSocket::Ptr async_socket) {
  ClientInfo *client = client_map_[async_socket];
  if (NULL != client->video_evtimer) {
    if (!evtimer_pending(client->video_evtimer, NULL)) {
      evtimer_del(client->video_evtimer);
    }
    event_free(client->video_evtimer);
  }
  delete client;
  client_map_.erase(async_socket);
}

void RtspServer::OnVideoTimer(AsyncSocket::Ptr async_socket) {
  ClientInfo *client = client_map_[async_socket];

  struct timeval during_tv = DURING_TIMEVAL(client->base_tv);
  during_tv = HTON_TIMEVAL(during_tv);
  memcpy(video_frame_data_, &during_tv, sizeof(during_tv));

  async_socket->AsyncSend(CMD_VIDEO_FRAME_DATA,
                          video_frame_data_, sizeof(video_frame_data_));
}
